package com.hp.gui.tab;

import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;

/*
 * DnDTabbedPane.java
 * Description: Tabs of this DnDTabbedPane can be dragged and dropped.
 */
public class DnDTabbedPane extends JTabbedPane {
    private static ImageIcon _iconCloseRed = new ImageIcon("close-red.gif");
    private static ImageIcon _iconCloseBlack = new ImageIcon("close-black.gif");
    private static ImageIcon _iconIDE = new ImageIcon("IDE.JPG");
    private static ImageIcon _iconChrome = new ImageIcon("chrome.PNG");
    //private final static int BUTTON_HOT = 2;
    private final static int BUTTON_NORMAL = 1;
    private final static int BUTTON_NULL = 0;
    private int _preSelectedTab = -1;
    private int _preRolloverTab = -1;
    private boolean _bClosed = true;
    private int dragTabIndex = -1;

    /**
     * DnDTabbedPane class constructor: Here we initialize DragSourceListener
     * and DragGestureListnener
     */
    public DnDTabbedPane() {
        super();
        //setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
        setAutoscrolls(true);
        initComponents();

        // set JTabbedPane as DropTarget and DragSource here.
        new DragSource().createDefaultDragGestureRecognizer(DnDTabbedPane.this, DnDConstants.ACTION_COPY_OR_MOVE, new DnDDragGestureListener());
        new DropTarget(DnDTabbedPane.this, DnDConstants.ACTION_COPY_OR_MOVE, new DnDTargetListener(), true);
    }

    /**
     * Override AddTab with arguments: title and Component to the JTabbedPane, add close button to tab
     *
     * @see javax.swing.JTabbedPane#addTab(java.lang.String, java.awt.Component)
     */
    @Override
    public void addTab(String title, Icon iconTab, Component comp) {
        add(comp);
        int index = indexOfComponent(comp);
        TabComponent tabComponent = new TabComponent(title, iconTab);
        setTabComponentAt(index, tabComponent);
        int indexTab = getSelectedIndex();
        setTabCloseButtonStatus(indexTab, BUTTON_NORMAL);
    }

    /**
     * Control the appearance of close buttons on tabs.
     */
    private void initComponents() {
        if (_bClosed) {
            // If current component needs to support the Close Button, we should add the
            // ChangeListener event to update the icon of Close Button according to the selected
            // status.
            addChangeListener(new ChangeListener() {
                @Override
                public void stateChanged(ChangeEvent e) {
                    int indexTab = getSelectedIndex();
                    if (_preSelectedTab != indexTab) {
                        // Remove the icon from previous selected tab
                        setTabCloseButtonStatus(_preSelectedTab, BUTTON_NULL);
                        // Set current selected tab to be BUTTON_NORMAL
                        setTabNormalByMouse(indexTab);
                        _preSelectedTab = indexTab;
                    } else {
                        //After moving, the original selected index in src is not changed,
                        //but a new tab is selected, we have to enable close button for
                        //this new tab.
                        setTabNormalByMouse(_preSelectedTab);
                    }
                }
            });
            addMouseListener(new MouseAdapter() {
                @Override
                public void mouseExited(MouseEvent e) {
                    // If user move mouse from TabbedComponent to Close button, it will trigger the
                    // mouseExited event, so we need to add the !mouseOnCloseButton(_preRolloverTab)
                    // condition. otherwise icon will be sparkling obviously
                    //if (_preRolloverTab >= 0 && _preRolloverTab != getSelectedIndex() && !mouseOnCloseButton(_preRolloverTab))
                    if (_preRolloverTab >= 0 && _preRolloverTab != getSelectedIndex()) {
                        // If mouse exited TabbedPane and not on the close button and
                        // current tab is not selected, it needs to clean up previous tab icon.
                        setTabCloseButtonStatus(_preRolloverTab, BUTTON_NULL);
                    }
                }
            });
            // If tab is too much, it will be shown by multilayer. The width of header
            // will be bigger than that of tab component. At this time, if user move
            // mouse to the empty area we need to set the rollovered tab to be normal.
            addMouseMotionListener(new MouseAdapter() {
                @Override
                public void mouseMoved(MouseEvent e) {
                    int pointingTabIndex = tabForCoordinate(e.getPoint());
                    if (_preRolloverTab >= 0 && _preRolloverTab != pointingTabIndex) {
                        // If mouse enter another tab, we need to update current close button
                        // and reset that of the leaving tab
                        if (_preRolloverTab == getSelectedIndex()) {} else {
                            setTabCloseButtonStatus(_preRolloverTab, BUTTON_NULL);
                        }
                        //if _preRolloverTab == getSelectedIndex(), it will always be with a close button.
						/*else
							setTabCloseButtonStatus(_preRolloverTab, BUTTON_NORMAL);*/
                    }
                    if (pointingTabIndex >= 0) {
                        setTabNormalByMouse(pointingTabIndex);
                        _preRolloverTab = pointingTabIndex;
                    }
                }
            });
        }
    }

    //Methods control behaviors of close button on tab.

    /**
     * Set the tab of this index to be normal with a black close button.
     * <p>
     * _button.setRolloverIcon(New ImageIcon("close-red.jpg")); is used to control
     * the appearance when the mouse is over the close button.
     */
    private void setTabNormalByMouse(int index) {
        setTabCloseButtonStatus(index, BUTTON_NORMAL);
    }

    /**
     * Reset the icon of close button on the indexed tab.
     */
    private void setTabCloseButtonStatus(int indexTab, int buttonStatus) {
        if (indexTab >= 0 && indexTab < getTabCount()) {
            TabComponent tabComponent = (TabComponent) getTabComponentAt(indexTab);
            if (tabComponent != null) {
                tabComponent.setCloseButtonStatus(buttonStatus);
            }
        }
    }

    /**
     * According to the Point, get the related index of Tab.
     */
    private int tabForCoordinate(Point p) {
        int index = getUI().tabForCoordinate(DnDTabbedPane.this, p.x, p.y);
        return index;
    }


    /**
     * Inner class DnDDragGestureListener implements DragGestureListener.
     * Start the drag here.
     */
    class DnDDragGestureListener implements DragGestureListener {
        @Override
        public void dragGestureRecognized(DragGestureEvent e) {
            //Get the drag source panel size
            int srcWidth = e.getComponent().getWidth();
            int srcHeight = e.getComponent().getHeight();
            System.out.println("Source size: " + srcWidth + "," + srcHeight);


            Point tabPt = e.getDragOrigin();
            dragTabIndex = indexAtLocation(tabPt.x, tabPt.y);
            if (dragTabIndex < 0) {
                System.out.println("Please drag a tab");
                return;
            }
            //GhostedDragImage image = new GhostedDragImage (e.getSource(),this,new Point(400,400),_iconIDE,new Point(5,5),true);
            try {
                System.out.println("<<Tab drag start...>>");
                Cursor cursor = getToolkit().createCustomCursor(_iconChrome.getImage(), new Point(0, 0), "usr");
                //dge.startDrag(cursor, t, this);
                //e.startDrag(DragSource.DefaultMoveDrop,_iconIDE.getImage(),new Point(5,5), new DnDTransferable(), new DnDSourceListener());
                e.startDrag(cursor, new DnDTransferable(), new DnDSourceListener());

            } catch (InvalidDnDOperationException idoe) {
                idoe.printStackTrace();
            }
        }
    }

    /**
     * Inner class DnDTransferable implements Transferable.
     * 1. Specify the data type we want to transfer.
     * 2. Also specify the getTransferData(DataFloavor flavor) that will be invoked in target's drop.
     */
    class DnDTransferable implements Transferable {
        DataFlavor FLAVOR = new DataFlavor(DataFlavor.javaRemoteObjectMimeType, "javaRemoteObjectMimeType");
        DataFlavor[] flavors = {FLAVOR};

        @Override
        public Object getTransferData(DataFlavor flavor) {
            System.out.println("Enter Transferable");

            if (flavor.isMimeTypeEqual(DataFlavor.javaRemoteObjectMimeType)) {
                System.out.println("True: This is an javaRemoteObjectMimeType");
                return DnDTabbedPane.this;
            } else {
                System.out.println("False: This is null");
                return null;
            }
        }

        @Override
        public DataFlavor[] getTransferDataFlavors() {
            return flavors;
        }

        @Override
        public boolean isDataFlavorSupported(DataFlavor flavor) {
            return flavor.isMimeTypeEqual(DataFlavor.javaRemoteObjectMimeType);
        }
    }

    /**
     * Inner class DnDSourceListener implements DragSourceListener:
     * All actions when this panel
     * is treated as a source while dragging
     */
    class DnDSourceListener implements DragSourceListener {
        @Override
        public void dragEnter(DragSourceDragEvent e) {
            Cursor cursor = getToolkit().createCustomCursor(_iconChrome.getImage(), new Point(0, 0), "usr");
            e.getDragSourceContext().setCursor(cursor);
            //e.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop);
        }

        @Override
        public void dragExit(DragSourceEvent e) {
            Cursor cursor = getToolkit().createCustomCursor(_iconCloseRed.getImage(), new Point(0, 0), "usr");
            e.getDragSourceContext().setCursor(cursor);
            //e.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop);
        }

        @Override
        public void dragOver(DragSourceDragEvent e) {
			/*Point tabPt = e.getLocation();
			SwingUtilities.convertPointFromScreen(tabPt, DnDTabbedPane.this);
			int currentIndex = indexAtLocation(tabPt.x, tabPt.y);
			if(currentIndex<0)
			{
				System.out.println(currentIndex + "out of index");
				Cursor cursor = getToolkit().createCustomCursor(_iconCloseRed.getImage(), new Point(0,0), "usr");
				e.getDragSourceContext().setCursor(cursor);
			}
			else
			{
				Cursor cursor = getToolkit().createCustomCursor(_iconChrome.getImage(), new Point(0,0), "usr");
				e.getDragSourceContext().setCursor(cursor);
			}*/
        }

        @Override
        public void dragDropEnd(DragSourceDropEvent e) {
        }

        @Override
        public void dropActionChanged(DragSourceDragEvent e) {
        }
    }

    /**
     * Target Inner class DnDTargetListener: All actions when this panel
     * is treated as a target while dragging.
     */
    class DnDTargetListener implements DropTargetListener {
        @Override
        public void dragEnter(DropTargetDragEvent ev) {
            //Get the panel size of the current target.
            Object obj = ev.getSource();
            Component targetComp = ((DropTarget) obj).getComponent();
            System.out.println("Current dropPanel size: " + targetComp.getWidth() + "," + targetComp.getHeight());


            System.out.println("Drag enter target");
            targetAcceptDrag(ev);
        }

        @Override
        public void dragExit(DropTargetEvent ev) {
        }

        @Override
        public void dragOver(DropTargetDragEvent ev) {
            Object target = ev.getSource();
            boolean targetIsJTabbedPane = ((DropTarget) target).getComponent() instanceof JTabbedPane;
            if (targetIsJTabbedPane) {
                targetAcceptDrag(ev);
            }
            // System.out.println("(X,Y):" + ev.getLocation().x + "," + ev.getLocation().y);
        }

        @Override
        public void dropActionChanged(DropTargetDragEvent ev) {
            targetAcceptDrag(ev);
        }

        @Override
        public void drop(DropTargetDropEvent ev) {
            try {
                Object target = ev.getSource();
                if (!bTargetIsTab(target)) {
                    return;
                }
                JTabbedPane srcTabbedPane;
                String title;
                Component component;
                DataFlavor[] flavors = ev.getTransferable().getTransferDataFlavors();
                System.out.println(flavors.length + " Flavor: " + flavors[0].toString());
                JTabbedPane destTabbedPane = (JTabbedPane) ((DropTarget) target).getComponent();
                //BasicTabbedPaneUI tabbedPaneUI = new BasicTabbedPaneUI (jtp);
                for (int i = 0; i < flavors.length; i++) {
                    if (flavors[i].isMimeTypeEqual(DataFlavor.javaRemoteObjectMimeType))// Accept tabs
                    {
                        System.out.println("<<Accept Tabs...>>");
                        // Get source tab's title and selectedComponent, add to the target
                        // JTabbedPane
                        Object obj = ev.getTransferable().getTransferData(flavors[i]);
                        if (!(obj instanceof JTabbedPane)) {
                            return;
                        }
                        srcTabbedPane = (JTabbedPane) obj;
                        title = getTitle(srcTabbedPane);
                        Icon icon = getIcon(srcTabbedPane);
                        component = srcTabbedPane.getSelectedComponent();
                        //Move original tab to the destination.
                        destTabbedPane.addTab(title, icon, component);
                        destTabbedPane.setSelectedIndex(destTabbedPane.getTabCount() - 1);

                        //If setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); we have to consider the condition that
                        //after adding tab to a lot of tabs, it has to auto scroll to the new added selected tab
                        //destTabbedPane.scrollRectToVisible(getSelectedTabBound(destTabbedPane));
                        //destTabbedPane.gett.scrollRectToVisible(getBounds());
                    } else if (flavors[i].isFlavorSerializedObjectType())// Accept the tree nodes
                    {
                        ImageIcon nodeIcon = new ImageIcon("IDE.JPG");
                        System.out.println("<<Accept TreeNode...>>");
                        Object obj = ev.getTransferable().getTransferData(flavors[i]);
                        if (!(obj instanceof ArrayList)) {
                            return;
                        }
                        ArrayList<TreePath> treeList = new ArrayList<TreePath>((ArrayList<TreePath>) obj);
                        for (int j = 0; j < treeList.size(); j++) {
                            destTabbedPane.addTab(treeList.get(j).getLastPathComponent().toString(), nodeIcon, new TextArea(treeList
                                    .get(j)
                                    .toString()));
                        }
                        destTabbedPane.setSelectedIndex(destTabbedPane.getTabCount() - 1);
                    }
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            ev.dropComplete(true);
            repaint();
        }


        //Methods help to drop

        /**
         * Accept the drag
         */
        private void targetAcceptDrag(DropTargetDragEvent ev) {
            ev.acceptDrag(ev.getDropAction());
        }

        private boolean bTargetIsTab(Object target) {
            return ((DropTarget) target).getDropTargetContext().getComponent() instanceof JTabbedPane;
        }

        /**
         * Get tab component's title from original tab
         *
         * @param tabbedPane
         * @return String
         */
        private String getTitle(JTabbedPane tabbedPane) {
            return ((TabComponent) tabbedPane.getTabComponentAt(tabbedPane.getSelectedIndex())).getTitle();
        }

        /**
         * Get tab component's Icon from original tab
         *
         * @param tabbedPane
         * @return Icon
         */
        private Icon getIcon(JTabbedPane tabbedPane) {
            return ((TabComponent) tabbedPane.getTabComponentAt(tabbedPane.getSelectedIndex())).getIcon();
        }
    }

    //Component to be used as tab component of JTabbedPane.

    /**
     * Component to be used as tab component of TabbedPane. It contains two JLabel to show the text
     * and icon and a JButton to close the tab it belongs to
     */
    class TabComponent extends JPanel {
        private static final long serialVersionUID = 2955071016071608002L;
        private JButton _button;
        private JLabel _labelTitle;
        private JLabel _labelIcon;
        private int _statusCloseButton = -1;

        public TabComponent(final String tabTitle, final Icon icon) {
            super();
            initComponents(tabTitle, icon);
        }

        /**
         * Get the icon of the icon label.
         *
         * @return
         */
        public Icon getIcon() {
            return _labelIcon.getIcon();
        }

        /**
         * Get the value of title label.
         *
         * @return
         */
        public String getTitle() {
            return _labelTitle.getText();
        }

        /**
         * Update the value of title label.
         */
        public void setTitle(String text) {
            _labelTitle.setText(text);
        }

        /**
         * Update the status of close button.
         */
        public void setCloseButtonStatus(int status) {
            if (_statusCloseButton != status) {
                setButtonIcon(status);
                _statusCloseButton = status;
            }
        }

        /**
         * Update the close button's icon directly.
         * BUTTON_HOT: Mouse is on the button.
         * BUTTON_NORMAL: Mouse is on the tab header but not on the button.
         * BUTTON_NULL: : Mouse is not on the tab header and button.
         */
        private void setButtonIcon(int buttonStatus) {
            if (_button != null) {
                // If icon is same as previous, JDK will not reset its icon.
                switch (buttonStatus) {
					/*case BUTTON_HOT:
						_button.setIcon(_iconCloseRed);
						break;*/
                    case BUTTON_NORMAL:
                        _button.setIcon(_iconCloseBlack);
                        break;
                    case BUTTON_NULL:
                        _button.setIcon(null);
                }
            }
        }

        /**
         * Init components supposed to be on the tab
         *
         * @param tabTitle
         * @param icon
         */
        private void initComponents(final String tabTitle, final Icon icon) {
            setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
            // set the component to be transparent.
            setOpaque(false);
            setBorder(new EmptyBorder(0, 0, 0, 0));

            // make tab icon
            if (icon != null) {
                _labelIcon = new JLabel();
                _labelIcon.setPreferredSize(new Dimension(19, 19));
                _labelIcon.setIcon(icon);
                add(_labelIcon);
                add(Box.createRigidArea(new Dimension(5, 5)));
            }

            // make tab title
            _labelTitle = new JLabel(tabTitle);
            add(_labelTitle);
            add(Box.createRigidArea(new Dimension(5, 5)));

            // make close button
            if (_bClosed) {
                _button = new JButton();
                _button.setBorderPainted(false);
                _button.setFocusable(false);
                // make it to be transparent
                _button.setContentAreaFilled(false);
                _button.setPreferredSize(new Dimension(19, 19));
                _button.setMargin(new Insets(0, 0, 0, 0));
                _button.setRolloverIcon(_iconCloseRed);
                _button.setToolTipText("Close");
                _button.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        clickCloseButton(e, indexOfTabComponent(TabComponent.this));
                    }
                });
                _button.addMouseListener(new MouseAdapter() {
                    @Override
                    public void mouseReleased(MouseEvent evt) {
                        // If user right/middle click the mouse, we should convert mouse event to
                        // the TabbedPane to change selection. If user left click the mouse, we do
                        // not need to convert mouse event to the TabbedPanel because the
                        // actionListener of Button will be executed to close tab.
                        if (evt.getButton() != MouseEvent.BUTTON1) {
                            convertMouseEventToTabbedPane(_button, evt);
                        }
                    }

                    @Override
                    public void mousePressed(MouseEvent evt) {
                        // If user right/middle click the mouse, we should convert mouse event to
                        // the TabbedPane to change selection. If user left click the mouse, we do
                        // not need to convert mouse event to the TabbedPanel because the
                        // actionListener of Button will be executed to close tab.
                        if (evt.getButton() != MouseEvent.BUTTON1) {
                            convertMouseEventToTabbedPane(_button, evt);
                        }
                    }
                });

                _button.addMouseMotionListener(new MouseAdapter() {
                    @Override
                    public void mouseMoved(MouseEvent evt) {
                        // convert the mouse move event to the the CLTabbedPane, it is used to keep the
                        // icon to be hot.
                        convertMouseEventToTabbedPane(_button, evt);
                    }
                });
                add(_button);
            }
        }
    }

    /**
     * Dispatch event from specified component to current DnDTabbedPane.
     */
    private void convertMouseEventToTabbedPane(Component comp, MouseEvent e) {
        MouseEvent evt = SwingUtilities.convertMouseEvent(comp, e, this);
        dispatchEvent(evt);
    }

    /**
     * This method is used to handle the click event on the close button.
     */
    protected void clickCloseButton(ActionEvent e, int tabClosedIndex) {
        remove(tabClosedIndex);
    }
}